package com.gmlive.common.videogiftview;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.net.Uri;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.widget.Toast;

import java.util.concurrent.atomic.AtomicBoolean;

public class GLTextureVideoView extends GLTextureView implements GLViewRenderer,
        SurfaceTexture.OnFrameAvailableListener, MediaPlayer.OnVideoSizeChangedListener {
    private static final String TAG = "GLTextureVideoView";

    private static final String VERTEX_SHADER =
            "attribute vec4 a_Position;\n" +
                    "attribute vec4 a_TexCoordinate;\n" +
                    "uniform mat4 mMatrix;\n" +
                    "uniform mat4 mTextureMatrix;\n" +
                    "varying vec4 v_TexCoord;\n" +
                    "\n" +
                    "void main()\n" +
                    "{\n" +
                    "    v_TexCoord = mTextureMatrix * a_TexCoordinate;\n" +
                    "    gl_Position = mMatrix * a_Position;\n" +
                    "}\n";

    private static final String FRAGMENT_SHADER =
            "#extension GL_OES_EGL_image_external : require\n" +
                    "precision mediump float;\n" +
                    "\n" +
                    "uniform samplerExternalOES u_Texture;\n" +
                    "varying mediump vec4 v_TexCoord;\n" +
                    "\n" +
                    "void main()\n" +
                    "{\n" +
                    "   vec4 tGray = texture2D(u_Texture, vec2(v_TexCoord.x / 2.0, v_TexCoord.y));\n" +
                    "   vec4 tColor = texture2D(u_Texture, vec2(v_TexCoord.x / 2.0 + 0.5, v_TexCoord.y));\n" +
                    "   gl_FragColor.rgb = tColor.rgb;\n" +
                    "   gl_FragColor.a = tGray.r;\n" +
                    "}\n";

    // all possible internal states
    private static final int STATE_ERROR = -1;
    private static final int STATE_IDLE = 0;
    private static final int STATE_PREPARING = 1;
    private static final int STATE_PREPARED = 2;
    private static final int STATE_PLAYING = 3;
    private static final int STATE_PAUSED = 4;
    private static final int STATE_PLAYBACK_COMPLETED = 5;

    private AtomicBoolean frameAvailable = new AtomicBoolean(false);
    private AtomicBoolean canDraw = new AtomicBoolean(false);

    private SurfaceTexture mRenderTexture;
    private int program;
    private int texture = 0;
    private GlUtil.Attribute[] attributes;
    private GlUtil.Uniform[] uniforms;
    private float[] projectionMatrix = new float[16];
    private float[] mSTMatrix = new float[16];
    private int screenWidth, screenHeight;

    private Handler mUiHandler;
    private SurfaceTexture mSurfaceTexture;
    private Surface mSurface;

    private MediaPlayer mMediaPlayer;
    private Uri mUri;
    private int mCurrentState = STATE_IDLE;
    private int mTargetState = STATE_IDLE;

    public GLTextureVideoView(Context context) {
        super(context);
    }

    public GLTextureVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GLTextureVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void init() {
        super.init();
        mUiHandler = new Handler(Looper.getMainLooper());
        setOpaque(false);
        setRenderer(this);

        setOnVideoSizeChangedListener(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        // 使用post确保按顺序执行任何onSurfaceTextureAvailable回调
        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                releaseSurface(mSurfaceTexture, mSurface);
                mSurfaceTexture = null;
                mSurface = null;
            }
        });
        super.onDetachedFromWindow();
    }

    // =====================  GLViewRenderer =====================
    private void onTextureCreated(SurfaceTexture surfaceTexture) {
        SurfaceTexture oldTexture = this.mSurfaceTexture;
        Surface oldSurface = this.mSurface;

        releaseSurface(oldTexture, oldSurface);

        this.mSurfaceTexture = surfaceTexture;
        this.mSurface = new Surface(mSurfaceTexture);
        openVideo();
    }

    private void releaseSurface(SurfaceTexture oldTexture, Surface oldSurface) {
        if (oldSurface != null) {
            oldSurface.release();
        }

        if (oldTexture != null) {
            oldTexture.release();
        }
    }

    @Override
    public void onSurfaceCreated() {
        Log.i(TAG, "onSurfaceCreated");
        // 链接shader脚本
        program = GlUtil.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        GlUtil.Attribute[] attributes = GlUtil.getAttributes(program);
        GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program);

        for (GlUtil.Attribute attr : attributes) {
            if ("a_Position".equals(attr.name)) {
                attr.setBuffer(
                        new float[]{
                                1f, -1f, 0f,
                                -1f, -1f, 0f,
                                1f, 1f, 0f,
                                -1f, 1f, 0f
                        }, 3
                );
            } else if ("a_TexCoordinate".equals(attr.name)) {
                attr.setBuffer(
                        new float[]{
                                1f, 0f,
                                0f, 0f,
                                1f, 1f,
                                0f, 1f
                        }, 2
                );
            }
        }

        this.attributes = attributes;
        this.uniforms = uniforms;

        // 创建texture
        texture = GlUtil.createExternalTexture();
        mRenderTexture = new SurfaceTexture(texture);
        mRenderTexture.setOnFrameAvailableListener(this);

        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                onTextureCreated(mRenderTexture);
            }
        });
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        Log.i(TAG, "onSurfaceChanged: width = " + width + ", height = " + height);
        GLES20.glViewport(0, 0, width, height);
        GlUtil.checkGlError();
        screenWidth = width;
        screenHeight = height;
    }

    @Override
    public void onDrawFrame() {
        if (frameAvailable.compareAndSet(true, false)) {
            if (mRenderTexture != null) {
                try {
                    mRenderTexture.updateTexImage();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                mRenderTexture.getTransformMatrix(mSTMatrix);
            }
        }

        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(0f, 0f, 0f, 0f);
        if (!canDraw.get()) {
            GLES20.glFinish();
            return;
        }

        GLES20.glUseProgram(program);
        GlUtil.checkGlError();

        if (uniforms != null) {
            for (GlUtil.Uniform uniform : uniforms) {
                uniform.setSamplerTexId(texture, 0);

                if ("mMatrix".equals(uniform.name)) {
                    GLES20.glUniformMatrix4fv(uniform.getLocation(), 1, false, projectionMatrix, 0);
                } else if ("mTextureMatrix".equals(uniform.name)) {
                    GLES20.glUniformMatrix4fv(uniform.getLocation(), 1, false, mSTMatrix, 0);
                } else if ("u_Texture".equals(uniform.name)) {
                    uniform.bind();
                }
            }
            GlUtil.checkGlError();
        }

        if (attributes != null) {
            for (GlUtil.Attribute attribute : attributes) {
                attribute.bind();
            }
            GlUtil.checkGlError();
        }

        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GlUtil.checkGlError();

        GLES20.glFinish();
    }
    // =====================  GLViewRenderer =====================

    // ================  OnFrameAvailableListener ================
    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        frameAvailable.compareAndSet(false, true);
        mUiHandler.post(new Runnable() {
            @Override
            public void run() {
                requestRender();
            }
        });
    }
    // ================  OnFrameAvailableListener ================

    private void updateProjection(int width, int height) {
        float screenRatio = screenWidth / (float) screenHeight;
        float videoRatio = (width >> 1) / (float) height;

        if (videoRatio < screenRatio) {
            Matrix.orthoM(
                    projectionMatrix,
                    0,
                    -1f,
                    1f,
                    -videoRatio / screenRatio,
                    videoRatio / screenRatio,
                    -1f,
                    1f
            );
        } else {
            Matrix.orthoM(
                    projectionMatrix,
                    0,
                    -screenRatio / videoRatio,
                    screenRatio / videoRatio,
                    -1f,
                    1f,
                    -1f,
                    1f
            );
        }
    }

    // =====================  MediaController ====================
    private MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener;
    private MediaPlayer.OnCompletionListener mOnCompletionListener;
    private MediaPlayer.OnErrorListener mOnErrorListener;
    private MediaPlayer.OnPreparedListener mOnPreparedListener;
    private MediaPlayer.OnInfoListener mOnInfoListener;

    public void setOnCompletionListener(MediaPlayer.OnCompletionListener completionListener) {
        mOnCompletionListener = completionListener;
    }

    public void setOnPreparedListener(MediaPlayer.OnPreparedListener preparedListener) {
        mOnPreparedListener = preparedListener;
    }

    public void setOnErrorListener(MediaPlayer.OnErrorListener errorListener) {
        mOnErrorListener = errorListener;
    }

    public void setOnInfoListener(MediaPlayer.OnInfoListener infoListener) {
        mOnInfoListener = infoListener;
    }

    public void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener videoSizeChangedListener) {
        mOnVideoSizeChangedListener = videoSizeChangedListener;
    }

    @Override
    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
        updateProjection(width, height);
    }

    public void setVideoPath(String path) {
        setVideoUri(Uri.parse(path));
    }

    public void setVideoUri(Uri uri) {
        mUri = uri;
        openVideo();
    }

    public void start() {
        if (isInPlaybackState()) {
            mMediaPlayer.start();
            mCurrentState = STATE_PLAYING;
        }
        mTargetState = STATE_PLAYING;
    }

    public void pause() {
        if (isInPlaybackState()) {
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.pause();
                mCurrentState = STATE_PAUSED;
            }
        }
        mTargetState = STATE_PAUSED;
    }

    public void resume() {
        openVideo();
    }

    public void seekTo(int mesc) {
        if (isInPlaybackState()) {
            mMediaPlayer.seekTo(mesc);
        }
    }

    public void stopPlayback() {
        if (mMediaPlayer != null) {
            mMediaPlayer.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            mCurrentState = STATE_IDLE;
            mTargetState = STATE_IDLE;
        }
    }

    public boolean isPlaying() {
        return isInPlaybackState() && mMediaPlayer.isPlaying();
    }

    private boolean isInPlaybackState() {
        return mMediaPlayer != null &&
                mCurrentState != STATE_ERROR &&
                mCurrentState != STATE_IDLE &&
                mCurrentState != STATE_PREPARING;
    }

    private void doOnFirstFrame() {
        canDraw.compareAndSet(false, true);
        Log.i(TAG, "onFirstFrame, canDraw = " + canDraw.get());
        requestRender();
    }

    private void doOnCompletion() {
        canDraw.compareAndSet(true, false);
        Log.i(TAG, "onCompletion, canDraw = " + canDraw.get());
        requestRender();
    }

    private void openVideo() {
        if (mUri == null || mSurfaceTexture == null) {
            // not ready for playback just yet, will try again later
            return;
        }

        release(false);

        try {
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.setOnPreparedListener(preparedListener);
            mMediaPlayer.setOnCompletionListener(completionListener);
            mMediaPlayer.setOnErrorListener(errorListener);
            mMediaPlayer.setOnInfoListener(infoListener);
            mMediaPlayer.setOnVideoSizeChangedListener(videoSizeChangedListener);

            mMediaPlayer.setDataSource(getContext(), mUri);
            mMediaPlayer.setSurface(mSurface);

            mMediaPlayer.prepareAsync();
            mCurrentState = STATE_PREPARING;
        } catch (Exception e) {
            Log.w(TAG, "Unable to open content: " + mUri, e);
            mCurrentState = STATE_ERROR;
            mTargetState = STATE_ERROR;

            if (mOnErrorListener != null) {
                mOnErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
            }
        }
    }

    private void release(boolean clearTargetState) {
        if (mMediaPlayer != null) {
            mMediaPlayer.reset();
            mMediaPlayer.release();

            mCurrentState = STATE_IDLE;
            if (clearTargetState) {
                mTargetState = STATE_IDLE;
            }

            mMediaPlayer = null;
        }
    }

    private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            Log.i(TAG, "onCompletion: mUri = " + mUri);
            mCurrentState = STATE_PLAYBACK_COMPLETED;
            mTargetState = STATE_PLAYBACK_COMPLETED;

            doOnCompletion();

            if (mOnCompletionListener != null) {
                mOnCompletionListener.onCompletion(mp);
            }
        }
    };

    private MediaPlayer.OnErrorListener errorListener = new MediaPlayer.OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mp, int what, int extra) {
            Log.i(TAG, "onError: " + what + ", " + extra);
            mCurrentState = STATE_ERROR;
            mTargetState = STATE_ERROR;

            if (mOnErrorListener != null) {
                mOnErrorListener.onError(mp, what, extra);
                return true;
            }

            if (getContext() != null) {
                Toast.makeText(getContext(), "播放失败:" + what + ", " + extra, Toast.LENGTH_SHORT).show();
            }

            return true;
        }
    };

    private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) {
            Log.i(TAG, "onPrepared: mTargetState = " + mTargetState);
            mCurrentState = STATE_PREPARED;
            if (mOnPreparedListener != null) {
                mOnPreparedListener.onPrepared(mMediaPlayer);
            }

            if (mTargetState == STATE_PLAYING) {
                start();
            }
        }
    };

    private MediaPlayer.OnVideoSizeChangedListener videoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() {
        @Override
        public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
            if (mOnVideoSizeChangedListener != null) {
                mOnVideoSizeChangedListener.onVideoSizeChanged(mp, width, height);
            }
        }
    };

    private MediaPlayer.OnInfoListener infoListener = new MediaPlayer.OnInfoListener() {
        @Override
        public boolean onInfo(MediaPlayer mp, int what, int extra) {
            Log.i(TAG, "onInfo: " + what + ", " + extra);
            if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
                doOnFirstFrame();
            }

            if (mOnInfoListener != null) {
                mOnInfoListener.onInfo(mp, what, extra);
            }
            return true;
        }
    };
    // =====================  MediaController ====================
}
